package com.ipan.poi.excel.importer;

import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_BACKUP;
import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_FAIL_FILE;
import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_FILE;
import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_IMPORT_FINISH;
import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_IMPORT_RESULT;
import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_IMPORT_START;
import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_PARSER_FAIL_ROW_NO;
import static com.ipan.poi.excel.log.XlsImportFileLogger.MSG_SAVE_FAIL_ROW_NO;
import static com.ipan.poi.utils.PoiAppFileLogger.MSG_FAIL;
import static com.ipan.poi.utils.PoiAppFileLogger.MSG_HR;
import static com.ipan.poi.utils.PoiAppFileLogger.MSG_SUCCESS;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ipan.poi.PoiConfig;
import com.ipan.poi.excel.XlsObjectFactory;
import com.ipan.poi.excel.WorkbookFactory;
import com.ipan.poi.excel.config.ImportConfiguration;
import com.ipan.poi.excel.config.XlsEntity;
import com.ipan.poi.excel.exception.XlsParserException;
import com.ipan.poi.excel.hander.XlsHander;
import com.ipan.poi.excel.log.XlsImportFailRecordLogger;
import com.ipan.poi.excel.log.XlsImportFileLogger;
import com.ipan.poi.excel.util.XlsUtils;

/**
 * XLS导入实现类 引入XlsHander对Excel做解析，用户可以自定义该接口；
 * 创建：使用工厂类XlsObjectFactory创建，每个线程访问时都需要重新创建；
 * 
 * @author iPan
 * @version 2013-09-15
 */
public class XlsImporter implements XlsImportable {
	// 常量定义
	private static final String IMPORT_SUCCESS_FILE_FMT = "[success]{0}_{1}";
	private static final String IMPORT_FAIL_FILE_FMT = "[fail]{0}_{1}";
	private static final String IMPORT_RECORD_FILE_FMT = "[record]{0}_{1}";
	private static final String IMPORT_LOGGER_FILE = "XlsImport.log";

	// 文件目录定义
	private String importDir = null;
	private String importSuccessDir = null;
	private String importFailDir = null;
	private Row title = null;
	private int titleRowIndex = 0;
	private int bodyRowIndex = 1;

	// 日志记录
	private XlsImportFileLogger fileLogger = null;
	private XlsImportFailRecordLogger recordLogger = null;
	private boolean logEnable = false;

	// 当前写入sheet
	private Sheet wtSheet = null;
	// Logger
	private Logger logger = LoggerFactory.getLogger(getClass());

	public XlsImporter() {
		String dir = PoiConfig.getInstance().getExcelLogDir();
		this.importDir = dir.replace("\\", "/");
		this.importSuccessDir = this.importDir + "/success";
		this.importFailDir = this.importDir + "/fail";
		// 创建文件记录
		fileLogger = new XlsImportFileLogger(this.getLoggerPath());
		this.logEnable = PoiConfig.getInstance().isXlsImporterLog();
	}

	public int getTitleRowIndex() {
		return titleRowIndex;
	}

	public void setTitleRowIndex(int titleRowIndex) {
		this.titleRowIndex = titleRowIndex;
	}

	public int getBodyRowIndex() {
		return bodyRowIndex;
	}

	public void setBodyRowIndex(int bodyRowIndex) {
		this.bodyRowIndex = bodyRowIndex;
	}

	public XlsResult importExcel(File file, String fileName, String className) {
		XlsHander hander = XlsObjectFactory.getDefaultXlsHander();
		return importExcel(file, fileName, className, hander);
	}

	public XlsResult importExcel(File file, String fileName, String className, boolean mlutiSheet) {
		XlsHander hander = XlsObjectFactory.getDefaultXlsHander();
		return importExcel(file, fileName, className, mlutiSheet, hander);
	}

	public XlsResult importExcel(File file, String fileName, String className, XlsHander hander) {
		return importExcel(file, fileName, className, false, hander);
	}

	public XlsResult importExcel(File file, String fileName, String className, boolean mlutiSheet, XlsHander hander) {
		// 对应的配置文件
		XlsEntity defEntity = ImportConfiguration.getInstance().getEntity(className);
		if (defEntity == null) {
			throw new RuntimeException("配置文件中找不到类型：" + className + "！");
		}
		
		// 文件名
		fileName = (StringUtils.isNotBlank(fileName)) ? fileName : file.getName();
		// 根据文件名创建XLS记录文件
		recordLogger = XlsImportFailRecordLogger.createXlsFailRecordLogger(this.getFailRecordPath(fileName));
		// 对应的实体类
		Class<?> entityClass = null;
		// 返回结果集
		XlsResult result = new XlsResult();
		// 加载实体类
		try {
			entityClass = Class.forName(className);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("找不到实体类[" + defEntity.getClassName() + "]！");
		}

		Workbook workbook = null;
		try {
			workbook = WorkbookFactory.create(file);
		} catch (InvalidFormatException e1) {
			throw new RuntimeException("不是有效的Excel文件！");
		} catch (IOException e1) {
			throw new RuntimeException("Excel文件找不到！");
		}
		int sheetNum = workbook.getNumberOfSheets();
		if (sheetNum < 1) {
			// 记录日志：导入成功
			this.successWork(file, fileName, result);
			// 记录日志：结束时间
			this.finishWork(result);
			return result;
		}

		// 初始化XlsHander
		hander.init();
		// 执行导入
		boolean status = true;
		try {
			// 记录日志：开始时间
			fileLogger.appendToCache(new String[] { fileLogger.getText(MSG_IMPORT_START, fileLogger.getDefaultDateStr()),
					fileLogger.getText(MSG_FILE, fileName) });
			// 是否支持多个Sheet
			if (mlutiSheet) {
				for (int i = 0; i < sheetNum; ++i) {
					if (!sheetToDb(workbook.getSheetAt(i), defEntity, entityClass, hander, result)) {
						status = false;
					}
				}
			} else {
				status = sheetToDb(workbook.getSheetAt(0), defEntity, entityClass, hander, result);
			}
		} catch (Exception e) {
			status = false;
		}

		// 最终状态设置
		result.setStatus(status);
		// 是否成功
		if (result.isStatus()) {
			this.successWork(file, fileName, result);
		} else {
			this.failWork(file, fileName, result);
		}

		// 记录日志：结束时间
		this.finishWork(result);
		// 导入文件另存为
		hander.saveAsFile(file);
		return result;
	}

	private boolean sheetToDb(Sheet sheet, XlsEntity defEntity, Class<?> entityClass, XlsHander xlsHander, XlsResult result)
			throws Exception {

		this.wtSheet = null;
		// 返回状态
		boolean status = true;
		// 获取行标，从0开始；
		int rowIndex = XlsUtils.getValidRowIndex(sheet);
		logger.debug("总行数：{}", rowIndex);
		// 至少两行
		if (rowIndex < 1) {
			return status;
		}

		// 当前Sheet的标题
		this.title = sheet.getRow(this.titleRowIndex);
		// 解析内容
		Row row = null;
		for (int i = bodyRowIndex; i <= rowIndex; ++i) {
			logger.debug("当前工作表:{}, 当前行：{}", sheet.getSheetName(), i);
			row = sheet.getRow(i);
			// 创建实体类
			Object obj = null;
			try {
				obj = entityClass.newInstance();
			} catch (Exception e) {
				status = false;
				throw new Exception("创建实体类[" + entityClass.getName() + "]失败！", e);
			}
			// 解析文件
			try {
				xlsHander.parser(defEntity, row, obj);
			} catch (XlsParserException e) {
				logger.error("行解析出错", e);
				status = false;
				// 失败记录加一
				result.addFailCount();
				// 失败记录写入XLS文件
				saveRecord(sheet, row, result.getFailCount());
				// 失败记录
				fileLogger.appendToCache(fileLogger.getText(MSG_PARSER_FAIL_ROW_NO, sheet.getSheetName(), i, e.getErrorCode()));
				continue;
			}
			// 保存实体
			try {
				// 插入数据库
				xlsHander.save(defEntity, obj, result);
			} catch (Exception e) {
				logger.error("记录保存出错！", e);
				status = false;
				// 失败记录加一
				result.addFailCount();
				// 失败记录写入XLS文件
				saveRecord(sheet, row, result.getFailCount());
				// 失败记录
				fileLogger.appendToCache(fileLogger.getText(MSG_SAVE_FAIL_ROW_NO, sheet.getSheetName(), i));
			}
		}

		return status;
	}

	private String getLoggerPath() {
		return this.importDir + "/" + IMPORT_LOGGER_FILE;
	}

	private String getImportSuccessPath(String fileName) {
		String name = MessageFormat.format(IMPORT_SUCCESS_FILE_FMT, getDateTime(), fileName);
		return this.importSuccessDir + "/" + name;
	}

	private String getImportFailPath(String fileName) {
		String name = MessageFormat.format(IMPORT_FAIL_FILE_FMT, getDateTime(), fileName);
		return this.importFailDir + "/" + name;
	}

	private String getFailRecordPath(String fileName) {
		String name = MessageFormat.format(IMPORT_RECORD_FILE_FMT, getDateTime(), fileName);
		return this.importFailDir + "/" + name;
	}

	public String getImportDir() {
		return this.importDir;
	}

	public String getImportSuccessDir() {
		return this.importSuccessDir;
	}

	public String getImportFailDir() {
		return this.importFailDir;
	}

	private void saveRecord(Sheet sheet, Row row, int failCount) {
		// 日志记录关闭
		if (!logEnable) {
			return ;
		}
		
		try {
			if (this.wtSheet == null) {
				this.wtSheet = this.recordLogger.createSheet(sheet.getSheetName());
			}
			// 第一条记录，先保存标题
			if (failCount == 1) {
				this.recordLogger.addRow(this.title);
			}
			this.recordLogger.addRow(row);
		} catch (Exception e) {
			logger.error("保存记录文件出错！", e);
		}
	}

	private void successWork(File file, String fileName, XlsResult result) {
		String filePath = this.getImportSuccessPath(fileName);
		this.copyFile(file, new File(filePath)); // 备份导入文件
		result.setImportFile(filePath);
		fileLogger.appendToCache(new String[] {
				fileLogger.getText(MSG_IMPORT_RESULT, MSG_SUCCESS, result.getTotalCount(), result.getUpdateCount(),
						result.getInsertCount(), result.getFailCount()), fileLogger.getText(MSG_BACKUP, filePath) });
	}

	private void failWork(File file, String fileName, XlsResult result) {
		String filePath = this.getImportFailPath(fileName);
		this.copyFile(file, new File(filePath)); // 备份导入文件
		result.setImportFile(filePath);
		String recordPath = recordLogger.getFilePath();
		result.setFailRecordFile(recordPath);
		fileLogger.appendToCache(new String[] {
				fileLogger.getText(MSG_IMPORT_RESULT, MSG_FAIL, result.getTotalCount(), result.getUpdateCount(),
						result.getInsertCount(), result.getFailCount()), fileLogger.getText(MSG_BACKUP, filePath),
				fileLogger.getText(MSG_FAIL_FILE, recordPath) });
	}

	private void finishWork(XlsResult result) {
		fileLogger.appendToCache(new String[] { fileLogger.getText(MSG_IMPORT_FINISH, fileLogger.getDefaultDateStr()), MSG_HR });
		result.setText(fileLogger.getCache().toString());
		// 日志记录是否开启
		if (logEnable) {
			fileLogger.flush();
		}
		// 清除缓存
		fileLogger.cleanCache();
		// 错误记录
		if (this.recordLogger.isOpen()) {
			this.recordLogger.write();
			this.recordLogger.close();
		}
	}

	private void copyFile(File srcDir, File destDir) {
		try {
			FileUtils.copyFile(srcDir, destDir);
		} catch (IOException e) {
			throw new RuntimeException("保存导入文件出错！", e);
		}
	}

	private String getDateTime() {
		return new Date().getTime() + "";
	}
}
